package handler

import (
	"context"
	"errors"
	"time"

	"github.com/golang/protobuf/ptypes/empty"
	"go.uber.org/zap"
	"golang.org/x/crypto/bcrypt"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"google.golang.org/protobuf/types/known/emptypb"
	"gorm.io/gorm"

	"mxshop_srvs/user_srv/dao/mysql"
	"mxshop_srvs/user_srv/model"
	proto "mxshop_srvs/user_srv/proto/gen/v1"
)

type Service struct {
	DB *mysql.Gorm
	proto.UnimplementedUserServer
}

// NewService defines a user service.
func NewService(db *mysql.Gorm) *Service {
	return &Service{
		DB: db,
	}
}

// ModelToResponse 格式转换
func ModelToResponse(user model.User) *proto.UserInfoResponse {
	// 在grpc的message中字段有默认值，你不能随便赋值nil进去，容易出错
	// 这里要搞清， 哪些字段是有默认值
	userInfoResp := &proto.UserInfoResponse{
		Id:       user.ID,
		PassWord: user.Password,
		NickName: user.NickName,
		Gender:   user.Gender,
		Role:     int32(user.Role),
		Mobile:   user.Mobile,
	}
	if user.Birthday != nil {
		userInfoResp.BirthDay = uint64(user.Birthday.Unix()) // .Unix()转换为时间戳
	}
	return userInfoResp
}

// GetUserList 获取用户列表
func (s *Service) GetUserList(_ context.Context, req *proto.PageInfo) (*proto.UserListResponse, error) {
	count, err := s.DB.GetUserCount()
	if err != nil {
		return nil, status.Error(codes.Internal, "")
	}

	rsp := &proto.UserListResponse{}
	//rsp.Total = int32(result.RowsAffected)
	rsp.Total = int32(count)

	// 分页
	users := s.DB.GetUserList(int(req.Pn), int(req.PSize))

	for _, user := range users {
		// 模型中的User和protobuf返回的格式不同，因此需要做个转换，因为考虑到可能会复用，因此把它提了出来。
		rsp.Data = append(rsp.Data, ModelToResponse(user))
	}
	return rsp, nil
}

// GetUserByMobile 通过手机号码查询用户
func (s *Service) GetUserByMobile(_ context.Context, req *proto.MobileRequest) (*proto.UserInfoResponse, error) {
	user, err := s.DB.GetUserByMobile(req.Mobile)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, status.Error(codes.NotFound, "")
		}
		return nil, status.Error(codes.Internal, "")
	}
	return ModelToResponse(user), nil
}

// GetUserById 通过ID查询用户
func (s *Service) GetUserById(_ context.Context, req *proto.IdRequest) (*proto.UserInfoResponse, error) {
	user, err := s.DB.GetUserByID(req.Id)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, status.Error(codes.NotFound, "")
		}
		return nil, status.Error(codes.Internal, "")
	}
	return ModelToResponse(user), nil
}

// CreateUser 添加用户
func (s *Service) CreateUser(_ context.Context, req *proto.CreateUserInfo) (*proto.UserInfoResponse, error) {
	// 查询是否存在
	_, err := s.DB.GetUserByMobile(req.Mobile)
	if err == nil {
		return nil, status.Error(codes.AlreadyExists, "")
	} else {
		if !errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, status.Error(codes.Internal, "")
		}
	}

	var user model.User
	user.Mobile = req.Mobile
	user.NickName = req.NickName

	// 密码加密
	hash, err := bcrypt.GenerateFromPassword([]byte(req.PassWord), bcrypt.DefaultCost)
	if err != nil {
		zap.L().Error("bcrypt generate from password failed", zap.Error(err))
		return nil, status.Error(codes.Internal, "")
	}
	user.Password = string(hash)
	// 创建用户
	err = s.DB.CreateUser(user)
	if err != nil {
		return nil, status.Error(codes.Internal, "")
	}
	return ModelToResponse(user), nil
}

// UpdateUser 更新用户
func (s *Service) UpdateUser(_ context.Context, req *proto.UpdateUserInfo) (*emptypb.Empty, error) {
	// 个人中心更新用户
	user, err := s.DB.GetUserByID(req.Id)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, status.Error(codes.NotFound, "")
		}
		return nil, status.Error(codes.Internal, "")
	}

	birthDay := time.Unix(int64(req.BirthDay), 0) // 把时间戳,转换为time.Time
	user.NickName = req.NickName
	user.Birthday = &birthDay
	user.Gender = req.Gender

	err = s.DB.UpdateUser(user)
	if err != nil {
		return nil, status.Error(codes.Internal, err.Error())
	}
	return &empty.Empty{}, nil
}

// CheckPassWord 检查密码
func (s *Service) CheckPassWord(_ context.Context, req *proto.PasswordCheckInfo) (*proto.CheckResponse, error) {
	err := bcrypt.CompareHashAndPassword([]byte(req.EncryptedPassword), []byte(req.Password))
	if err != nil {
		return &proto.CheckResponse{Success: false}, nil
	}
	return &proto.CheckResponse{Success: true}, nil
}
